import { defineGlobalProperty } from '@/ayaka'
import { buildArcPoint, buildPath } from './buildPoint'
import { parsePath } from './parsePath'
import { PointTuple } from './types'

/**
 * Visual 状态
 */
export interface VisualState {
    /**
     * 水平位移量
     */
    dx: number
    /**
     * 垂直位移量
     */
    dy: number
    /**
     * 顺时针旋转角度，单位为弧度
     */
    angle: number
    /**
     * x 轴缩放比例
     */
    scaleX: number
    /**
     * y 轴缩放比例
     */
    scaleY: number
    /**
     * 样式
     */
    style: PolyStyle
}

/**
 * Visual对象，扩展了游戏中的 RoomVisual
 */
export default class Visual extends RoomVisual {
    /**
     * visual缓存
     */
    static visualCache = new Map<string, string>()

    /**
     * 状态栈
     */
    #stateStack: VisualState[] = []

    /**
     * 当前状态
     */
    #state: VisualState

    /**
     * 当前保存的路径点
     */
    #points: PointTuple[] = []

    constructor(roomName: string) {
        super(roomName)

        this.#state = this.reset().#state
    }

    /**
     * 绘制路径点
     *
     * @param style 路径样式
     */
    draw(style: PolyStyle = {}): Visual {
        this.#state.style = Object.assign(this.#state.style, style)
        this.poly(this.#points, this.#state.style)
        this.#points = []
        return this
    }

    /**
     * 绘制路径，如需 svg 图标见：https://www.iconfont.cn/
     *
     * @param path 路径字符串
     * @param style 路径样式
     */
    drawPath(path: string, style?: PolyStyle): Visual {
        const { dx, dy, scaleX, scaleY } = this.#state
        const points = buildPath(parsePath(path)).map((p) => {
            return [p[0] * scaleX + dx, p[1] * scaleY + dy] as PointTuple
        })

        this.poly(points, style)
        return this
    }

    /**
     * 添加圆弧
     *
     * @param x 圆心 x 坐标
     * @param y 圆心 y 坐标
     * @param radius 半径
     * @param startAngle 起始角度，单位为弧度
     * @param endAngle 结束角度，单位为弧度
     * @param anticlockwise 是否逆时针
     * @param stepAngle 每隔多少弧度取一个点，单位为度，默认为 15 度
     * @param opts 额外选项
     */
    arc(
        x: number,
        y: number,
        radius: number,
        startAngle: number,
        endAngle: number,
        anticlockwise: boolean,
        stepAngle?: number,
        opts: { startPoint?: PointTuple; endPoint?: PointTuple } = {},
    ): Visual {
        if (opts.startPoint) this.#points.push(opts.startPoint)

        this.#points.push(...buildArcPoint(x, y, radius, startAngle, endAngle, anticlockwise, stepAngle))

        if (opts.endPoint) this.#points.push(opts.endPoint)

        return this
    }

    /**
     * 添加圆角矩形
     *
     * @param x 矩形左上角 x 坐标
     * @param y 矩形左上角 y 坐标
     * @param width 矩形宽度
     * @param height 矩形高度
     * @param radius 圆角半径
     */
    roundRect(x: number, y: number, width: number, height: number, radius: number | number[]): Visual {
        if (typeof radius === 'number') {
            radius = [radius]
        }

        // tl = top left radius
        // tr = top right radius
        // br = bottom right radius
        // bl = bottom left radius
        // min = shortest side radius of the rectangle
        let tr: number, br: number, bl: number
        const min = Math.min(width, height) / 2
        const tl = (tr = br = bl = Math.min(min, radius[0]))
        if (radius.length === 2) {
            tr = bl = Math.min(min, radius[1])
        }
        if (radius.length === 3) {
            tr = bl = Math.min(min, radius[1])
            br = Math.min(min, radius[2])
        }
        if (radius.length === 4) {
            tr = Math.min(min, radius[1])
            br = Math.min(min, radius[2])
            bl = Math.min(min, radius[3])
        }

        this.arc(x + tl, y + tl, tl, -Math.PI, -Math.PI / 2, false)
        this.#points.push([x + tl, y])
        this.#points.push([x + width - tr, y])
        this.arc(x + width - tr, y + tr, tr, -Math.PI / 2, 0, false)
        this.#points.push([x + width, y + tr])
        this.#points.push([x + width, y + height - br])
        this.arc(x + width - br, y + height - br, br, 0, Math.PI / 2, false)
        this.#points.push([x + width - br, y + height])
        this.#points.push([x + bl, y + height])
        this.arc(x + bl, y + height - bl, bl, Math.PI / 2, Math.PI, false)
        this.#points.push([x, y + height - bl])
        this.#points.push([x, y + tl])

        return this
    }

    /**
     * 设置位移量
     *
     * @param dx 水平位移量
     * @param dy 垂直位移量
     */
    setOffset(dx: number, dy: number): Visual {
        this.#state.dx = dx
        this.#state.dy = dy
        return this
    }

    /**
     * 设置旋转角度
     */
    setAngle(angle: number): Visual {
        this.#state.angle = angle
        return this
    }

    /**
     * 设置缩放比例
     *
     * @param scaleX x 轴缩放比例
     * @param scaleY y 轴缩放比例
     */
    setScale(scaleX: number, scaleY: number): Visual {
        this.#state.scaleX = scaleX
        this.#state.scaleY = scaleY
        return this
    }

    /**
     * 设置样式
     *
     * @param style 样式
     */
    setStyle(style: PolyStyle): Visual {
        this.#state.style = style
        return this
    }

    /**
     * 重置状态
     */
    reset(): Visual {
        this.#state = {
            dx: 0,
            dy: 0,
            angle: 0,
            scaleX: 1,
            scaleY: 1,
            style: {},
        }
        return this
    }

    /**
     * 清空状态栈
     */
    clearStack(): Visual {
        this.#stateStack = []
        return this
    }

    /**
     * 保存当前绘制状态
     */
    save(): Visual {
        this.#stateStack.push({ ...this.#state, style: { ...this.#state.style } })
        return this
    }

    /**
     * 恢复上一个绘制状态
     */
    restore(): Visual {
        this.#state = this.#stateStack.pop() || this.#state
        return this
    }

    /**
     * 缓存 visual
     */
    setCache(key: string): Visual {
        Visual.visualCache.set(key, this.export())
        return this
    }

    /**
     * 获取缓存
     */
    getCache(key: string): string | undefined {
        return Visual.visualCache.get(key)
    }

    /**
     * 从缓存中读取 visual
     *
     * @param visualStr export导出或从缓存中读取的字符串
     */
    load(visualStr: string): Visual {
        this.import(visualStr)
        return this
    }

    /**
     * 清空缓存
     */
    clearCache(key?: string): Visual {
        key ? Visual.visualCache.delete(key) : Visual.visualCache.clear()
        return this
    }

    /**
     * 绘制图形，优先使用缓存，没有则根据回调函数绘制，同时保存到缓存中
     */
    drawCache(key: string, callback: (visual: Visual) => unknown): Visual {
        const cache = this.getCache(key)
        if (cache) {
            return this.load(cache)
        }

        callback(this)
        return this.setCache(key)
    }

    /**
     * rgb 转 16进制
     */
    rgbToHex(r: number, g: number, b: number): string {
        return '#' + [r, g, b].map((x) => x.toString(16).padStart(2, '0')).join('')
    }

    /**
     * 绘制一张图片，最大只能画大概 7000个像素点，即 80 * 80 的图片，无缓存 cpu 消耗 20 左右
     */
    drawImage(
        x: number,
        y: number,
        imageData: [number, number, number][],
        cols: number,
        opts: { width?: number } = {},
    ): Visual {
        // 每个像素点的大小
        const pixelSize = opts.width ? Number((opts.width / cols).toFixed(2)) : 1

        // 从 x，y 开始，对于图片数据的每个像素点，都使用 roomVisual 的 rect 方法绘制一个像素大小的矩形
        let px = x
        let py = y
        imageData.forEach((pixel, index) => {
            const [r, g, b] = pixel
            this.rect(px, py, pixelSize, pixelSize, { fill: this.rgbToHex(r, g, b) })
            px += pixelSize
            if ((index + 1) % cols === 0) {
                px = x
                py += pixelSize
            }
        })

        return this
    }
}

defineGlobalProperty('Visual', {
    value: Visual,
})
